//http://closure-compiler.appspot.com/home минимизация
var formLib = {
	//about: "Library for dynamic check of the html forms.\nVersion: 1.0\nAuthor: Денис Орлов 2012г.", 
	//toString: function() {return this.about;},
	"errorClass": "error", //имя класса для отображения ошибок,
	messElIdPostfix: '_'+'formLib'+'_message', //постфикс для Id контейнера вывода сообщений,
	// символ в шаблоне маски mask, означающий место любого неопред. символа согласно регулярн. выражению проверки 
	maskSymbolPlace: '#', // напр. String.fromCharCode(9) - tab
	"REQUIRED_FIELD": 'обязательное поле', //requiredMess = 'обязательное поле'
	"messageFormat":'{FIELDLABEL}: {MESSAGE}',// формат вывода сообщений

	/**
	 * Проверка полей формы form по объекту правил rulesObj,
	 * где ключ - имя поля, а значение, т.е. само правило, либо объект, либо строка - наименование поля (label)
	 *
	 * Пример объекта и использования:
	 * <code>
	 *     rulesObj = {
	 *		Name: {
	 *			label:'Имя',
	 *			required: 0, // необязательное: проверяется соответствие regexp только заполненного поля
	 *			regexp:/^[А-яЁёA-Za-z \-]+$/i
	 *			,e_message: 'только буквы, пробелы и тире'
	 *		},
	 *		Email: {
	 *			label:'Email',
	 *			required: 1, // обязательное: проверяется и заполненность поля и соответствие regexp,
	 *			regexp:/^([a-z0-9_\-]+\.)*[a-z0-9_\-]+@([a-z0-9][a-z0-9\-]*[a-z0-9]\.)+[a-z]{2,4}$/i
	 *            ,e_message: 'некорректный E-mail адрес'
	 *        },
	 *        Message:'Сообщение'// только строка наименование(label) поля
	 *    };
	 *    if( formLib.validateFields( someFormElement, rulesObj, formLib.outErrObj ) ) return false;
	 * </code>
	 * Логика проверки поля( вынесена в метод getFieldError ):
	 * 1.Eсли правило - строка, она содержит наименование поля и проверяется только его заполненость.
	 * 2.Eсли правило - объект, он должен содержать ключи типа:
	 *         label:'Имя', //наименование поля, попадает в сообшение об ошибке
	 *         required: 1, //обязательность поля, определяет логику проверки
	 *         regexp:/^[А-яЁёA-Za-z \-]+$/i, // регулярка для проверки методом regexp.test
	 *         e_message: 'только буквы, пробелы и тире' // сообщение при несоответствии regexp
	 *     если required: 1 - проверяется и заполненость поля и соответствие regexp,
	 *     если required: 0 - проверяется соответствие regexp только если поле заполнено
	 * 3.Вместо regexp может быть regexpArr - массив объектов с ключами regexp и e_message, например
	 *     regexpArr:[
	 *         {regexp:/^[\s\S]{10,}$/, e_message: 'слишком короткое сообщение'}
	 *         ,{regexp:/^[\s\S]{10,250}$/, e_message: 'слишком длинное сообщение'}
	 *     ]
	 * в этом случае проверяется массив до первого не соответствия, т.е. первые элементы-условия приоритетнее.
	 * 4.Правило-объект, может содержать ключ func:
	 *     func: function( form, rulesObj, elemName ){ ... }
	 * Ключ func должен содержать функцию проверки, которая принимает form, rulesObj, elemName
	 * и должна! возвращать string  - cообщение об ошибке или пустую строку,
	 * для совместимости с return getFieldError, внутри которой она вызывается
	 * 5.Правило-объект может содержать Фильтр - ключ filter:
	 * 	filter:{
	 * 		regexp: /^(\d{1,2}:?|\d{1,2}:\d{1,2}|\d{1,2}:\d{1,2}[ \-]*|\d{1,2}:\d{1,2} - \d{1,2}:?|\d{0,2}:\d{0,2} - \d{0,2}:\d{0,2})$/,
			mask: '##:## - ##:##'
	 * 	}
	 * Обязательный параметр filter.regexp определяет регулярку, по которой значение поля будет проверятся в процессе ввода(!),
	 * поэтому такой регулярке могут понадобиться варианты для неблокирования ввода неполной строки, 
	 * 	 напр. через |(или): /^(0\.?|0.0\.?|0.0.0)$/, или через ?: /^\d{0,2}:?\d{0,2} ?-? ?\d{0,2}:?\d{0,2}$/,
	 * Если filter.regexp: true, вместо регулярки, то используется регулярка самого правила( повторное использование кода). 
	 * Необязательный параметр mask определяет маску фиксированой(!) длины, т.е. позиции ввода и автоподстановки:
	 * 	Неопределенные элементы(по умолчанию #, параметр maskSymbolPlace), - резервируют место под ручной ввод.	 
	 * 	Определенные элементы mask(в примере ":","-" и пробелы) подставляются автоматически при вводе.	 
	 * Таким образом, filter без mask работает только как фильтр символов, а с mask - как маска ввода. 
	 * При неверном вводе в обход фильтра, строка последовательно обрезается от конца, пока не станет соответсвовать регулярке или не опустеет.
	 * 	 
	 * Фильтр включается методами activateFilter, activateFilters (для всех элементов объекта rulesObj).
	 * Автопроверка при потере фокуса включается методами activateOnBlurElem, activateOnBlurElems (для всех элементов объекта rulesObj).		 
	 *
	 * Найденные ошибки собираются в объект errorsObj, формат - имя_поля: сообщение об ошибке
	 * @param {HTMLElement} form проверяемая форма
	 * @param {Object} rulesObj объект правил для полей формы(см. пример выше)
	 * @param {Function} callBackFunc функция(напр. вывода ошибок), принимающая errorsObj и форму
	 * @return {Object} объект errorsObj или null, если ошибки не найдены
	 */
	"validateFields": function( form, rulesObj, callBackFunc ) {
		//formLib.clearMessages( form );

		var errorsObj = {}, e_cnt = 0;
		for( var key in rulesObj ) {
			var mess = formLib.getFieldError( form, rulesObj, key );
			if ( mess != '' ) e_cnt++;
			errorsObj[ key ] = mess;
		}

		if ( typeof callBackFunc == 'function' ) callBackFunc.call( window, errorsObj, form );

		return e_cnt ? errorsObj : null;
	},
	
	/**
	 *	Получение элемента по имени.
	 * 	 
	 * @param {HTMLElement} parent родительский элемент или 0, default document
	 * @param {string} name имя элемента
	 * @return {HTMLElement} или null
	 */
	"getElemByName": function( parent, name ){
		//return Array.prototype.shift.call( (parent ? parent: document).getElementsByName( name ) ) || null;
		parent = parent ? parent: document;
		if(parent.elements) return parent.elements[ name ] || null;
		if(parent.getElementsByName) {
			var coll = parent.getElementsByName( name );
			if( coll.length ) return coll[ coll.length-1 ];//последний или единственный
		}
		return null;
	},

	/**
	 * Проверка поля key формы form по объекту rulesObj, логика: validateFields
	 *
	 * @param {HTMLElement}    form        проверяемая форма
	 * @param {Object}        rulesObj    объект правил для полей формы (@see validateFields)
	 * @param {string}        elemName    имя поля, он же и ключ в объекте полей rulesObj
	 * @return {string}        cообщение об ошибке или пустую строку
	 * @see validateFields
	 */
	"getFieldError": function( form, rulesObj, elemName ) {
		var elem = formLib.getElemByName( form, elemName );//;form.elements[elemName] || null;
		if ( elem === null ) return '';

		var mess = '', flabel='', 
			rule = rulesObj[elemName], 
			val = elem.value, ph = elem.getAttribute( 'title' ); //вместо placeholder

		if ( typeof rule == 'object' ) {// объект со свойствами, проверяем досконально
			flabel = rule.label || ''; 
			if ( (rule.required && val == '') || val == ph ) //(обязательное и пустое) или равно placeholder
				mess = formLib.REQUIRED_FIELD;
				else 
			if ( rule.required || val != '' ) {// обязательное или ( необязательное и...) непустое
				if ( rule.regexpArr instanceof Array ) {
					for( var i = 0; i < rule.regexpArr.length; i++ )
						if ( !(rule.regexpArr[i].regexp).test( val ) ) {
							mess = rule.regexpArr[i].e_message;
							break;
						}
				} else {
					if ( !rule.regexp.test( val ) ) mess = rule.e_message;
				}
				//
				if ( mess == '' && typeof rule.func == 'function' ) {
					var fmess = rule.func.call( window, form, rulesObj, elemName );
					if ( fmess != '' ) mess = fmess;
				}
			}
		} else // строка заголовка поля, проверяем только заполнено или нет
		if ( val == '' || val == ph ) {
			flabel = rule || ''; 
			mess = formLib.REQUIRED_FIELD;
		}
		//форматируем
		if(mess!=''){
			var formatMess = formLib.messageFormat.replace( new RegExp('{FIELDLABEL}', 'g'), flabel );
			formatMess = formatMess.replace( new RegExp('{MESSAGE}', 'g'), mess );
			mess = formatMess;
		}
		return mess;
	},

	/**
	 * Вывод найденных ошибок алертом,
	 * используется как callback функция в validateFields
	 *
	 * @param {Object} errorsObj объект с ошибками, формат - имя_поля: сообщение об ошибке, 
 	 * @see validateFields
	 */
	"alertErrObj": function( errorsObj ) {
		var mess = '', key;
		for( key in errorsObj ) if( errorsObj[ key ]!='' ) mess += '\n   ' + errorsObj[ key ];
		alert( 'Исправьте пожалуйста: ' + mess );
	},

	/**
	 * Вывод найденных ошибок рядом с элементами формы с пом. метода outMess,
	 * исп. как callback функция в validateFields
	 *
	 * @param {Object} errorsObj объект с ошибками, @see validateFields
	 * @param {HTMLElement} form форма
	 */
	"outErrObj": function( errorsObj, form ) {
		for( var key in errorsObj ) formLib.outMess( form, key, errorsObj[ key ], formLib.errorClass );
	},

	/**
	 * Вывод строки сообщения(напр. ошибки) рядом с элементом формы,
	 * создает контейнер для строки с форматом ID : form.name+'_'+inpName+formLib.messElIdPostfix,
	 *
	 * @param {HTMLElement} form форма
	 * @param {string} inpName имя элемента
	 * @param {string} messStr строка сообщения
	 * @param {string} elClass класс элемента-контейнера сообщения	 
	 */
	"outMess": function( form, inpName, messStr, elClass ) {

		var field, el, 
			elId = form.name + '_' + inpName + formLib.messElIdPostfix,
			elClass = elClass || '';

		if ( field = formLib.getElemByName( form, inpName ) ) {//form.elements[ inpName ] 

			if ( el = document.getElementById( elId ) ) {
				// remove from DOM but not delete, course IE 7,8 appendChild + innerHTML = fail
				el.parentNode.removeChild( el );
			} else {
				el = document.createElement( 'div' );
				el.id = elId;
			}

			el.innerHTML = messStr;//newEl.appendChild( document.createTextNode(messStr) );
			el.className = elClass;
			field.parentNode.appendChild( el );
		}
	},

	/**
	 * Очистка элементов-ошибок формы outMess
	 * удаление элементов созданных методом 
	 * т.е. с форматом ID : form.name+'_'+inpName+formLib.messElIdPostfix,	 
	 *
	 * @param {HTMLElement} form форма
	 * @see outMess
	 */
	"clearMessages": function( form ) {
		for( var i = 0; i < form.elements.length; i++ ) {
			var inpName = form.elements[i].name, 
			elErr = document.getElementById( form.name + '_' + inpName + formLib.messElIdPostfix );
			//or elErr.innerHTML = ''; without collapsing in IE
			if ( elErr ) elErr.parentNode.removeChild( elErr );
		}
	},
	
	/**
	 * очистка значений элементов, 
	 * с использованием включающего и исключающего списков - массивов имен
	 * приоритет: исключающий exlList, т.е. очистить все кроме этих,
	 * затем включающий incList, т.е. очистить только эти,
	 * если не переданы оба списка  - очищается все
	 * 
	 * @param {HTMLElement} form форма
	 * @param {Array} incList строка сообщения
	 * @param {Array} exlList класс элемента-контейнера сообщения
	 */
	"clearValues": function( form, incList, exlList ) { 
	
		var array_search=function( needle, haystack ){
			for( var i=0; i<haystack.length; i++) if( haystack[i]==needle ) return i;
			return -1;
		}

		for(var i = 0; i < form.elements.length; i++) {
			var name = form.elements[i].name;
			if( exlList){
				if( array_search( name, exlList )< 0 ) form.elements[i].value = '';
			}
			else
			if( incList ){
				if( array_search( name, incList )>-1 ) form.elements[i].value = '';
			}
			else 
			form.elements[i].value = '';
		}
	},

	/**
	 * Привязка проверки элемента формы inpName методом getFieldError на событие onblur
	 * Вывод собщений об ошибках методом outMess с подстановкой formLib.errorClass, если сообщение не пустое
	 * 
	 * @param {HTMLElement} form проверяемая форма
	 * @param {string} inpName имя элемента
	 * @param {Object} rulesObj объект правил для полей формы( validateFields )
	 * @see validateFields	 
	 */
	"activateOnBlurElem": function( form, inpName, rulesObj ){
		var elem = formLib.getElemByName( form, inpName ); //form.elements[inpName];
		if ( elem ){
			var handler = function() {
				var hadValue = this.value;
				this.onblur = function() {
				// чтобы не нервировать "ошибками" еще до попыток заполнения:
				// если было и есть пустое, и оно обязательное( не объект или object.required ) выходим,
					if ( hadValue == '' && this.value == '' && ( typeof rulesObj[this.name] != 'object' || rulesObj[this.name].required ) ) return;
					// но реагируем на опустевшие необязательные, стирая "ошибки", и непустые обязательные
					var mess = formLib.getFieldError( form, rulesObj, this.name ),
						messClass = mess!='' ? formLib.errorClass : '';
					formLib.outMess( form, this.name, mess, messClass );
				}
			}
			formLib.addEvent( elem, 'focus', handler );
		}
	},
	
	/**
	 * Привязка проверки элементов формы form методом activateOnBlurElem на событие onblur
	 * 
	 * @param {HTMLElement} form проверяемая форма
	 * @param {Object} rulesObj объект правил для полей формы(  validateFields )	
	 * @see activateOnBlurElem	 
	 */
	"activateOnBlurElems": function( form, rulesObj ) {
		for( var key in rulesObj ) formLib.activateOnBlurElem( form, key, rulesObj );
	},
	
	/**
	 * Активация фильтра ввода элемента формы inpName 
	 * 
	 * @param {HTMLElement} form проверяемая форма
	 * @param {string} inpName имя элемента
	 * @param {Object} rulesObj объект правил для полей формы( @see validateFields )
	 * @param {Function} onFilterError функция(напр. вывода ошибок), принимает форму, имя элемента, вставляемый символ, флаг ошибки
	 */	
	"activateFilter": function( form, inpName, rulesObj, onFilterError ){
		var elem = formLib.getElemByName( form, inpName );//form.elements[inpName];
		if ( elem && typeof rulesObj[inpName].filter =='object'){
			var type = 'keypress',
			
			changeHandler = function(e){
				e = e || window.event;
				var filterObj = rulesObj[this.name].filter,
					filterReg = filterObj.regexp===true ? rulesObj[this.name].regexp : filterObj.regexp,
					newval = this.value;
				for( var i=newval.length-1; i>=0 && !filterReg.test(newval); i--) {		
					var ins='', mch='';
					if( filterObj.mask!=undefined){
						mch=filterObj.mask.substr(i,1);
						if(  mch!='' &&  mch!=formLib.maskSymbolPlace ) ins = mch;	
					} 
					newval = newval.substr(0,i)+ins+newval.substr(i+1);
				}
				this.value = newval;
			},
			
			keypressHandler = function(e){
				e = e || window.event;
				if(e.keyCode==13) return;
				var filterObj = rulesObj[this.name].filter,
					filterReg = filterObj.regexp===true ? rulesObj[this.name].regexp : filterObj.regexp,
					carPos = formLib.getCaretPosition( this ), //позиция каретки
					isError = false, //ошибка еше не случилась
					ich = formLib.getChar(e),//вставляемый символ
					oldval=this.value, newval='',
					// предыдущие и последующие месту вставки подстроки
					prestr = oldval.substr(0,carPos), 
					poststr = '',
					oldich= '';// прежний или последующий символ, сохраняется или вырезается в зависимости от маски
					
				// если указана маска, подменяем символ за кареткой текущим вводом, т.к. маска по опред. фиксир. длины
				if( filterObj.mask!=undefined  ){
					poststr = oldval.substr(carPos+1);// начинаем постстроку дальше, за символом
					oldich = oldval.substr(carPos,1) ;// но сохраним его на всякий случай(см. ниже)
				} else{
					poststr = oldval.substr(carPos); // начинаем постстроку С символа, и оставляем его пустым, т.к. как уже включили в постстроку
				}
					
				carPos++;
				if( filterReg.test( prestr+ich+poststr ) ){
					newval  = prestr+ich+poststr;
				}else
				if( filterReg.test( prestr+ich+oldich+poststr ) ){ // вот и понадобился сохраненный oldich,
					newval  = prestr+ich+oldich+poststr;  // т.к. оказался элементом самой маски(определенные элементы mask @see validateFields)
				}else{
					isError = true, //ошибка случилась
					newval = prestr+oldich+poststr; //вернули все как было,
					carPos--; // и каретку - то же обратно
				}
				// автоматически подставляем определенные элементы mask и двигаем каретку за них
				if(filterObj.mask!=undefined ){
					var chPos = carPos, 
						mch = filterObj.mask.substr( chPos,1);
					while( chPos<filterObj.mask.length && mch!='' &&  
						mch!=formLib.maskSymbolPlace && filterReg.test( newval.substr(0,chPos)+mch ) ){
						newval = newval.substr(0,chPos)+mch+newval.substr(chPos+1);//заменяем
						chPos++; carPos++;
						mch = filterObj.mask.substr( chPos,1);
					}
					
					if(newval.length>=filterObj.mask.length) isError = false; //не ошибка, просто кончилась строка
				}
				
				this.value = newval;
				formLib.setCaretPosition( this, carPos );
				if( typeof onFilterError=='function' ) onFilterError.call( window, form, this.name, ich, isError );
				
				return false;
			};
			
			elem.onkeypress = keypressHandler;
			formLib.addEvent( elem, 'change', changeHandler );
		}
	},
	
	/**
	 * Активация фильтра ввода элементов формы form 
	 * 
	 * @param {HTMLElement} form проверяемая форма
	 * @param {Object} rulesObj объект правил для полей формы( validateFields )
	 * @param {Function} onFilterError функция(напр. вывода ошибок), принимает форму, имя элемента, вставляемый символ, флаг ошибки	 
	 */		
	"activateFilters": function( form, rulesObj, onFilterError ){
		for( var key in rulesObj ) formLib.activateFilter( form, key, rulesObj, onFilterError );	
	},
	
	// *** Впомогательные функции
	
	/**
	* Добавление обработчика события элементу
	*/
	addEvent: function( elem, type, handler){
		if(document.addEventListener)
		elem.addEventListener( type, handler, false );
			else
		elem.attachEvent( 'on'+type, function(){ handler.call(elem, window.event); } );
	},
	
	/**
	* Получение позиции каретки
	*/
	getCaretPosition: function (ctrl) {
 
		var CaretPos = 0;
		// IE Support
		if(document.selection){
	 
			ctrl.focus ();
			var Sel = document.selection.createRange ();
	 
			Sel.moveStart ('character', -ctrl.value.length);
	 
			CaretPos = Sel.text.length;
		}else // Firefox support
		if (ctrl.selectionStart || ctrl.selectionStart == '0')
			CaretPos = ctrl.selectionStart;
		return (CaretPos);
	},
 	/**
	* Установка позиции каретки
	*/
	setCaretPosition: function(ctrl, pos){
	 
		if(ctrl.setSelectionRange){
			ctrl.focus();
			ctrl.setSelectionRange(pos,pos);
		}else 
		if (ctrl.createTextRange){
			var range = ctrl.createTextRange();
			range.collapse(true);
			range.moveEnd('character', pos);
			range.moveStart('character', pos);
			range.select();
		}
	},
	
	/**
	* Получение символа при keypress
	* event.type должен быть keypress
	*/	
	getChar: function(event) {
		if (event.which == null) {  // IE
			if (event.keyCode < 32) return null; // спец. символ
			return String.fromCharCode(event.keyCode) 
		} 

		if (event.which!=0 && event.charCode!=0) { // все кроме IE
			if (event.which < 32) return null; // спец. символ
			return String.fromCharCode(event.which); // остальные
		} 

		return null; // спец. символ
	},
	/**
	 * Сборка тела запроса из значений элементов формы для отправки через AJAX
	 *
	 * @param {HTMLElement} form форма
	 */
	"getRequestBody": function( form ) {
		var aParams = new Array();
		for( var i = 0; i < form.elements.length; i++ ) {
			var sParam = encodeURIComponent( form.elements[i].name );
			sParam += "=";
			sParam += encodeURIComponent( form.elements[i].value );
			aParams.push( sParam );
		}
		return aParams.join( "&" );
	}
};